using System.Diagnostics;
using System.Threading.Tasks;

namespace FreshMeat.Xaml
{
   /// <summary>
   /// Asynchronous task-based command that takes a parameter of <typeparamref name="T"/>.
   /// </summary>
   /// <typeparam name="T">Type type of the parameter passed to the command.</typeparam>
   public class AsyncCommand<T> : Command<T>
   {
      readonly CommandExecuteTaskDelegate<T> executeTaskDelegate;

      /// <summary>
      /// Creates instance using specified parameters and where the parameter is automatically converted
      /// to <typeparamref name="T"/> by a simple cast.
      /// </summary>
      /// <param name="canExecuteDelegate">Determines whether command can execute.</param>
      /// <param name="executeTaskDelegate">Performs whatever work should be done when the command is executed.</param>
      /// <param name="context">Context which is typically shared by multiple commands so only one can execute at a time or null to create a context for use by this command.</param>
      /// <param name="name">Name for debugging purposes or null to generate a unique name.</param>
      public AsyncCommand(CommandCanExecuteDelegate<T> canExecuteDelegate, CommandExecuteTaskDelegate<T> executeTaskDelegate, OperationContext context = null, string name = null)
         : this(canExecuteDelegate, executeTaskDelegate, parameter => (T)parameter, context, name)
      {
      }

      /// <summary>
      /// Creates instance using specified parameters.
      /// </summary>
      /// <param name="canExecuteDelegate">Determines whether command can execute.</param>
      /// <param name="executeTaskDelegate">Performs whatever work should be done when the command is executed.</param>
      /// <param name="conversionDelegate">Converts parameter to type <typeparamref name="T"/>.</param>
      /// <param name="context">Context which is typically shared by multiple commands so only one can execute at a time or null to create a context for use by this command.</param>
      /// <param name="name">Name for debugging purposes or null to generate a unique name.</param>
      public AsyncCommand(CommandCanExecuteDelegate<T> canExecuteDelegate, CommandExecuteTaskDelegate<T> executeTaskDelegate, CommandParameterConversionDelegate<T> conversionDelegate, OperationContext context = null, string name = null)
         : base(canExecuteDelegate, conversionDelegate, context, name)
      {
         this.executeTaskDelegate = executeTaskDelegate;
      }

      protected override void Execute(T parameter, Stopwatch stopwatch)
      {
         AsyncExecute(parameter, stopwatch);
         Log("Started async delegate ({0:F0}ms)", stopwatch.ElapsedMilliseconds);
      }

      async void AsyncExecute(T parameter, Stopwatch stopwatch)
      {
         using (Context)
         {
            await executeTaskDelegate(parameter);
            Log("Completed async delegate ({0:F0}ms)", stopwatch.ElapsedMilliseconds);
         }
      }
   }

   /// <summary>
   /// Asynchronous Task-based command that doesn't use a parameter.
   /// </summary>
   public class AsyncCommand : AsyncCommand<object>
   {
      public AsyncCommand(CommandCanExecuteDelegate canExecute, CommandExecuteTaskDelegate execute, OperationContext context = null, string name = null)
         : base(_ => canExecute(), _ => execute(), value => value, context, name)
      {
      }
   }

   /// <summary>
   /// Delegate for method to be executed by a <see cref="Command{T}"/>.
   /// </summary>
   public delegate Task CommandExecuteTaskDelegate<in T>(T parameter);

   /// <summary>
   /// Delegate for method to be executed by a <see cref="Command"/>.
   /// </summary>
   public delegate Task CommandExecuteTaskDelegate();
}